package com.cgs.query.handler.statement

import com.cgs.query.configuration.QueryConfiguration
import com.cgs.query.exception.QueryException
import com.cgs.query.executor.ExecutionContext
import com.cgs.query.handler.ResultSetHandler
import java.sql.Connection
import java.sql.Statement
import java.io.Reader
import java.io.BufferedReader
import java.util.stream.Collectors
import java.io.PrintWriter
import java.io.StringReader
import java.sql.SQLException
import java.util.regex.Pattern


class ScriptStatementHandler(
        config: QueryConfiguration,
        executionContext: ExecutionContext
) : BaseStatementHandler(config, executionContext) {

    private var connection: Connection? = null
    private var scriptRunner: ScriptRunner? = null

    override fun instantiateStatement(connection: Connection): Statement {
        this.connection = connection
        return connection.createStatement()
    }

    override fun parameterize(statement: Statement) {
    }

    override fun update(statement: Statement): Int {
        execute(statement)
        return statement.updateCount
    }

    override fun query(statement: Statement): List<Any> {
        return execute(statement)
    }

    override fun execute(statement: Statement): List<Any> {
        return (scriptRunner ?: ScriptRunner(connection
                ?: throw QueryException("连接不能为空"), statement, config.resultSetHandler, mappedStatement.limit).apply {
            if (!config.configProperties.printSql) this.logWriter = null
            this.runScript(StringReader(this@ScriptStatementHandler.boundSql.sql))
            this.sendFullScript = true
        })!!.getSimpleResults()
    }
}

class ScriptRunner(
        private val connection: Connection,
        private val statement: Statement,
        private val resultSetHandler: ResultSetHandler,
        private val limit: Int?
) {

    var stopOnError: Boolean = false
    var throwWarning: Boolean = false
    var autoCommit: Boolean = false
    var sendFullScript: Boolean = false
    var removeCRs: Boolean = false
    var escapeProcessing = true
    var fullLineDelimiter: Boolean = false
    var logWriter: PrintWriter? = PrintWriter(System.out)
    var errorLogWriter: PrintWriter? = PrintWriter(System.err)
    var delimiter = DEFAULT_DELIMITER
    val commands = arrayListOf<String>()
    private val result = arrayListOf<Any>()

    init {
        statement.apply { this.setEscapeProcessing(escapeProcessing) }
    }

    fun runScript(reader: Reader) {
        setAutoCommit()
        try {
            if (sendFullScript) {
                executeFullScript(reader)
            } else {
                executeLineByLine(reader)
            }
        } finally {
            rollbackConnection()
        }
    }

    private fun executeFullScript(reader: Reader) {
        val script = StringBuilder()
        try {
            val lineReader = BufferedReader(reader)
            lineReader.lines().map { it }.collect(Collectors.joining(LINE_SEPARATOR))
            val command = script.toString()
            println(command)
            commands.add(command)
            executeStatement(command)
            commitConnection()
        } catch (e: Exception) {
            val message = "Error executing: $script.  Cause: $e"
            printlnError(message)
            throw QueryException(message, e)
        }

    }

    private fun executeLineByLine(reader: Reader) {
        val command = StringBuilder()
        try {
            val lineReader = BufferedReader(reader)
            lineReader.lines().forEach { handleLine(command, it) }
            commitConnection()
            checkForMissingLineTerminator(command)
        } catch (e: Exception) {
            val message = "Error executing: $command.  Cause: $e"
            printlnError(message)
            throw QueryException(message, e)
        }

    }

    fun closeConnection() {
        try {
            connection.close()
        } catch (e: Exception) {
            // ignore
        }

    }

    private fun setAutoCommit() {
        try {
            if (autoCommit != connection.autoCommit) {
                connection.autoCommit = autoCommit
            }
        } catch (t: Throwable) {
            throw QueryException("Could not set AutoCommit to $autoCommit. Cause: $t", t)
        }

    }

    private fun commitConnection() {
        try {
            if (!connection.autoCommit) {
                connection.commit()
            }
        } catch (t: Throwable) {
            throw QueryException("Could not commit transaction. Cause: $t", t)
        }

    }

    private fun rollbackConnection() {
        try {
            if (!connection.autoCommit) {
                connection.rollback()
            }
        } catch (t: Throwable) {
            // ignore
        }

    }

    private fun checkForMissingLineTerminator(command: StringBuilder?) {
        if (command != null && command.toString().trim { it <= ' ' }.isNotEmpty()) {
            throw QueryException("Line missing end-of-line terminator ($delimiter) => $command")
        }
    }

    @Throws(SQLException::class)
    private fun handleLine(command: StringBuilder, line: String) {
        val trimmedLine = line.trim { it <= ' ' }
        when {
            lineIsComment(trimmedLine) -> {
                val matcher = DELIMITER_PATTERN.matcher(trimmedLine)
                if (matcher.find()) {
                    delimiter = matcher.group(5)
                }
                println(trimmedLine)
            }
            commandReadyToExecute(trimmedLine) -> {
                command.append(line.substring(0, line.lastIndexOf(delimiter)))
                command.append(LINE_SEPARATOR)
                println(command)
                commands.add(command.toString())
                executeStatement(command.toString())
                command.setLength(0)
            }
            trimmedLine.isNotEmpty() -> {
                command.append(line)
                command.append(LINE_SEPARATOR)
            }
        }
    }

    private fun lineIsComment(trimmedLine: String): Boolean {
        return trimmedLine.startsWith("//") || trimmedLine.startsWith("--")
    }

    private fun commandReadyToExecute(trimmedLine: String): Boolean {
        // issue #561 remove anything after the delimiter
        return !fullLineDelimiter && trimmedLine.contains(delimiter) || fullLineDelimiter && trimmedLine == delimiter
    }

    @Throws(SQLException::class)
    private fun executeStatement(command: String) {
        var hasResults = false
        var sql = command
        if (removeCRs) {
            sql = sql.replace("\r\n".toRegex(), "\n")
        }
        if (stopOnError) {
            hasResults = statement.execute(sql)
            if (throwWarning) {
                // In Oracle, CRATE PROCEDURE, FUNCTION, etc. returns warning
                // instead of throwing exception if there is compilation error.
                val warning = statement.warnings
                if (warning != null) {
                    throw warning
                }
            }
        } else {
            try {
                hasResults = statement.execute(sql)
            } catch (e: SQLException) {
                val message = "Error executing: $command.  Cause: $e"
                printlnError(message)
            }

        }
        printResults(statement, hasResults)
    }

    private fun printResults(statement: Statement, hasResults: Boolean) {
        result.add(resultSetHandler.handleResultSet(hasResults, statement, limit))
    }

    fun getSimpleResults() = if (result.size == 1) (result[0] as List<Any>) else result

    private fun print(o: Any) {
        if (logWriter != null) {
            logWriter!!.print(o)
            logWriter!!.flush()
        }
    }

    private fun println(o: Any) {
        if (logWriter != null) {
            logWriter!!.println(o)
            logWriter!!.flush()
        }
    }

    private fun printlnError(o: Any) {
        if (errorLogWriter != null) {
            errorLogWriter!!.println(o)
            errorLogWriter!!.flush()
        }
    }

    companion object {

        private val LINE_SEPARATOR = System.getProperty("line.separator", "\n")

        private val DEFAULT_DELIMITER = ";"

        private val DELIMITER_PATTERN = Pattern.compile("^\\s*((--)|(//))?\\s*(//)?\\s*@DELIMITER\\s+([^\\s]+)", Pattern.CASE_INSENSITIVE)
    }

}